<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Raspberry Pi RC controller</title>

    <style>
.flex-container {
    display: -webkit-flex;
    display: flex;
}
.button {
    opacity: 1;
    font-size: .875rem;
    margin-bottom: .5rem;
    padding-left: 1rem;
    padding-right: 1rem;
    padding-top: .5rem;
    padding-bottom: .5rem;
    background-color: #000;
    color: #fff;
    text-decoration: none;
    display: inline-block;
    border-radius: .25rem;
}
.control-button {
    border: 1px solid black;
    text-align: center;
    font-size: 300%;
}
.unset {
    box-shadow: 0 0 3px red;
}
#help {
    width: 600px;
    display: none;
}
    </style>

    <script type="text/ECMAScript">
var optionToId = {
    'frequency': 'frequency',
    'synchronization_burst_us': 'sync-burst-us',
    'synchronization_spacing_us': 'sync-spacing-us',
    'total_synchronizations': 'total-syncs',
    'signal_burst_us': 'signal-burst-us',
    'signal_spacing_us': 'signal-spacing-us',
    'forward': 'forward',
    'forward_left': 'forward-left',
    'forward_right': 'forward-right',
    'left': 'left',
    'right': 'right',
    'reverse': 'reverse',
    'reverse_left': 'reverse-left',
    'reverse_right': 'reverse-right'
};
var forward = false;
var reverse = false;
var left = false;
var right = false;

function loadFile(file) {
    var reader = new FileReader();
    reader.onload = function (e) {
        try {
            var json = JSON.parse(e.target.result);
            loadConfig(json);
        } catch (error) {
            alert('Unable to read JSON: ' + error);
            return;
        }
    };
    reader.readAsText(file);
}
function loadConfigFromServer() {
    var request = new XMLHttpRequest();
    request.onload = function() {
        try {
            if (this.status === 200) {
                var config = JSON.parse(this.responseText);
                loadConfig(config);
            } else {
                config = {};
            }
            // We might have gotten redirected here from watch.html, in which
            // case, the parameters for the directions will be empty. Pop open
            // the parameters div so that the user can enter them.
            if (config.forward === undefined) {
                toggleOptions(true);
            }
        } catch (error) {
            alert('Unable to read JSON: ' + error);
            return;
        }
    };
    request.open('GET', window.location.origin + '/parameters.json');
    request.send();
}
function loadConfig(config) {
    for (var option in optionToId) {
        if (optionToId.hasOwnProperty(option)) {
            value = config[option];
            const element = document.getElementById(optionToId[option]);
            if (element !== null) {
                if (value === undefined) {
                    // Let the user know that this still needs to be set
                    element.classList.add('unset');
                } else {
                    element.value = value;
                }
            }
        }
    }
}
function handleFileSelect(evt) {
    evt.preventDefault();
    var file = evt.target.files[0];
    loadFile(file);
}
function formatControlMessage() {
    var frequency = parseFloat(document.getElementById('frequency').value);
    var deadFrequency = frequency > 40 ? 26.995 : 49.830;

    var command = [{
        'frequency': frequency,
        'dead_frequency': deadFrequency,
        'burst_us': parseFloat(document.getElementById('sync-burst-us').value),
        'spacing_us': parseInt(document.getElementById('sync-spacing-us').value),
        'repeats': parseInt(document.getElementById('total-syncs').value)
    }];

    var forwardCount = forward ? 2 : reverse ? 0 : 1;
    var leftCount = left ? 0 : right ? 2 : 1;
    var burstIdLookup = [
        ['reverse-left', 'reverse', 'reverse-right'],
        ['left', '', 'right'],  // Not moving
        ['forward-left', 'forward', 'forward-right']
    ];
    var burstId = burstIdLookup[forwardCount][leftCount];
    if (burstId !== '') {
        var burstSignals = parseInt(document.getElementById(burstId).value);
        command.push({
            'frequency': frequency,
            'dead_frequency': deadFrequency,
            'burst_us': parseFloat(document.getElementById('signal-burst-us').value),
            'spacing_us': parseInt(document.getElementById('sync-spacing-us').value),
            'repeats': burstSignals
        });
    }
    return JSON.stringify(command);
}
function sendControlMessage(evt) {
    if (evt !== undefined) {
        evt.preventDefault();
    }

    var commandEndPoint = window.location.origin + '/command/';

    var xhr = new XMLHttpRequest();
    xhr.open('POST', commandEndPoint, true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.onreadystatechange = function() {
        if (xhr.readyState === XMLHttpRequest.DONE) {
            // Any success code is fine
            if (!('' + xhr.status).startsWith('2')) {
                writeMessage('Bad response received, HTTP code: ' + xhr.status);
            }
        }
    };
    xhr.onerror = function(err) {
        writeMessage('There was an error communicating with the backend: ' + err);
    };
    xhr.send(formatControlMessage());
}
function keyHandler(evt, down) {
    evt = evt || window.evt;
    var key = evt.key || evt.srcElement || evt.keyCode;
    if (key === undefined && evt.changedTouches) {
        key = event.changedTouches[0].target;
    }
    if (key === undefined && evt.type.startsWith('mouse')) {
        key = evt.target;
    }
    var needUpdate = false;  // We'll use this to ignore key repeats

    if (key === 'ArrowUp' || key === 38 || key === document.getElementById('forward-button')) {  // Up
        needUpdate = (forward !== down);
        forward = down;
    } else if (key === 'ArrowDown' || key === 40 || key === document.getElementById('reverse-button')) {  // Down
        needUpdate = (reverse !== down);
        reverse = down;
    } else if (key === 'ArrowLeft' || key === 37 || key === document.getElementById('left-button')) {  // Left
        needUpdate = (left !== down);
        left = down;
    } else if (key === 'ArrowRight' || key === 39 || key === document.getElementById('right-button')) {  // Right
        needUpdate = (right !== down);
        right = down;
    }
    if (needUpdate) {
        sendControlMessage();
        evt.preventDefault();
    }
}
function toggleOptions(popOpen) {
    var heading = document.getElementById('parameters-heading');
    var text = heading.innerText;
    var options = document.getElementById('parameters-options');
    var save = document.getElementById('save-options');

    // Was this called from onclick?
    if (popOpen !== true && popOpen !== false) {
        if (options.style.display === 'none') {
            popOpen = true;
        } else {
            popOpen = false;
        }
    }

    if (popOpen) {
        heading.innerText = text.substr(0, text.length - 1) + String.fromCharCode(0x25BC);
        options.style.display = 'block';
        save.style.display = 'inline';
    } else {
        heading.innerText = text.substr(0, text.length - 1) + String.fromCharCode(0x25B2);
        options.style.display = 'none';
        save.style.display = 'none';
    }
}

var clearMessageTimeoutId = null;
function writeMessage(message) {
    var messageElement = document.getElementById('message');
    messageElement.innerText = message;
    if (clearMessageTimeoutId !== null) {
        window.clearTimeout(clearMessageTimeoutId);
    }
    clearMessageTimeoutId = window.setTimeout(
            function() { writeMessage(''); },
            2000);
}

function init(){
document.getElementById('parameters-heading').addEventListener('click', toggleOptions);
document.getElementById('parameter-file').addEventListener('change', handleFileSelect);
document.getElementById('help-header').addEventListener('click', function(event) {
    document.getElementById('help').style.display = 'block';
});

loadConfigFromServer();

var element;
['forward-button', 'reverse-button', 'left-button', 'right-button'].forEach(function (id) {
    var element = document.getElementById(id);
    ['mousedown', 'touchstart'].forEach(function (action) {
        element.addEventListener(action, function (evt) { keyHandler(evt, true); });
    });
    ['mouseup', 'touchend'].forEach(function (action) {
        element.addEventListener(action, function (evt) { keyHandler(evt, false); });
    });
});
var body = document.getElementById('body');
body.addEventListener('keydown', function (evt) { keyHandler(evt, true); });
body.addEventListener('keyup', function (evt) { keyHandler(evt, false); });
for (var option in optionToId) {
    element = document.getElementById(optionToId[option]);
    element.addEventListener('focus', function (evt) {
        evt.preventDefault();
        evt.target.classList.remove('unset');
    }.bind(this));
}

document.getElementById("save").addEventListener('click', function(event) {
    event.preventDefault();
    let fullConfig = {};
    for (let option in optionToId) {
        if (optionToId.hasOwnProperty(option)) {
            fullConfig[option] = parseFloat(document.getElementById(optionToId[option]).value);
        }
    };
    var request = new XMLHttpRequest();
    request.onerror = function() {
        writeMessage("Unable to save configuration");
    };
    request.onreadystatechange = function() {
        console.log(request.readyState);
        if (request.readyState === XMLHttpRequest.DONE) {
            writeMessage("Configuration saved");
        }
    };
    request.open('POST', window.location.origin + '/save/');
    request.setRequestHeader('Content-Type', 'application/json');
    request.send(JSON.stringify(fullConfig));
}, false);

}
    </script>
</head>

<body id="body" onload="init();">
    <h1>Pi-RC controller</h1>

    <div class="flex-container" style="height: 200px;">
        <div class="flex-container" style="width: 50%; height: 100%; display: block;">
            <div id="forward-button" class="control-button" style="height: 50%; width: 100%; line-height: 100px;">&#x2191;</div>
            <div id="reverse-button" class="control-button" style="height: 50%; width: 100%; line-height: 100px;">&#x2193;</div>
        </div>
        <div class="flex-container" style="width: 50%;">
            <div id="left-button" class="control-button" style="height: 100%; width: 50%; line-height: 200px;">&#x2190;</div>
            <div id="right-button" class="control-button" style="height: 100%; width: 50%; line-height: 200px;">&#x2192;</div>
        </div>
    </div>

    <p id="message"></p>

<h2 id="help-header" class="center">Help &#x25BC;</h2>
<div id="help" class="center">
    <p>
This page will let you control the RC car and search for the individual
drive commands. Each command (e.g. forward, reverse and left) consists of
a certain number of repeated signals. Try setting the "forward" input box
below to different numbers (10-80), and then clicking the up arrow or
pushing the up arrow on your keyboard and see if the car drives forward.
When it does, move to the next input box and continue on.
    </p>
    <p>
Most cars receivers have a tolerance, where they will accept a value +/-
1. Try 3 values and use the middle one. The next command will usually be 6
or more repeated signals away.
    </p>
    <p>
Once you have all of the commands set, click the "Save parameters" button. The
next time you load this page, the parameters will be automatically loaded, and
you can use the arrows at the top or your keyboard's arrow keys to drive the
car.
    </p>
</div>

    <h2 id="parameters-heading">Parameters &#x25BC;</h2>
    <div id="save-options" class="content center" style="display: none;">
        <a id="save" class="button">Save parameters</a>
    </div>
    <div id="parameters-options" style="display: none;">
    <table>
        <tr><td><h3>Commands</h3></td></tr>
        <tr>
            <td>Forward</td>
            <td>
                <input id="forward" type="number" min="5" max="100" step="1" value="11"/>
            </td>
        </tr>
        <tr>
            <td>Forward left</td>
            <td>
                <input id="forward-left" type="number" min="5" max="100" step="1" value="27"/>
            </td>
        </tr>
        <tr>
            <td>Forward right</td>
            <td>
                <input id="forward-right" type="number" min="5" max="100" step="1" value="33"/>
            </td>
        </tr>
        <tr>
            <td>Left</td>
            <td>
                <input id="left" type="number" min="5" max="100" step="1" value="59"/>
            </td>
        </tr>
        <tr>
            <td>Right</td>
            <td>
                <input id="right" type="number" min="5" max="100" step="1" value="64"/>
            </td>
        </tr>
        <tr>
            <td>Reverse</td>
            <td>
                <input id="reverse" type="number" min="5" max="100" step="1" value="39"/>
            </td>
        </tr>
        <tr>
            <td>Reverse left</td>
            <td>
                <input id="reverse-left" type="number" min="5" max="100" step="1" value="51"/>
            </td>
        </tr>
        <tr>
            <td>Reverse right</td>
            <td>
                <input id="reverse-right" type="number" min="5" max="100" step="1" value="45"/>
            </td>
        </tr>
        <tr><td><h3>Settings</h3></td></tr>
        <tr>
            <td>Frequency</td>
            <td>
                <input id="frequency" type="number" min="10" max="100" step="0.005" value="40.0"/>
            </td>
        </tr>
        <tr>
            <td>Synchronization burst microseconds</td>
            <td>
                <input id="sync-burst-us" type="number" min="50" max="10000" step="10" value="1200"/>
            </td>
        </tr>
        <tr>
            <td>Synchronization spacing microseconds</td>
            <td>
                <input id="sync-spacing-us" type="number" min="50" max="10000" step="10" value="400"/>
            </td>
        </tr>
        <tr>
            <td>Total synchronizations</td>
            <td>
                <input id="total-syncs" type="number" min="2" max="10" step="1" value="4"/>
            </td>
        </tr>
        <tr>
            <td>Signal burst microseconds</td>
            <td>
                <input id="signal-burst-us" type="number" min="50" max="10000" step="10" value="400"/>
            </td>
        </tr>
        <tr>
            <td>Signal spacing microseconds</td>
            <td>
                <input id="signal-spacing-us" type="number" min="50" max="10000" step="10" value="400"/>
            </td>
        </tr>
    </table>
    </div>
    <label for="parameter-file">
        <b>Load JSON parameter file</b>
        <input id="parameter-file" name="parameter-file" type="file" accept="application/json"/>
    </label>

</body>
</html>
